;
; Lab5.asm
;
; Author: Evan Zimmerman <zimmermane@msoe.edu>
;         Kevin Duer <duerk@msoe.edu>
; Date: 10/13/12
; Course: CE4920-001
; Instructor: Barnicki
;
; Purpose: Simulates a basic operating system by switching between
;          four processes which output their process number to
;          PORTC. Processes are context switched every 20ms by
;          the Timer1 output compare match interrupt routine.
;          Also includes process information.
;		   Monitors for a power failure event across multiple  
;          processes and uses thewatchdog timer to handle such an event
; 

.include "m32def.inc"

.equ P0PID = 0x0060     ; P0 PID
.equ P0PRI = 0x0061     ; P0 Priority
.equ P0SC  = 0x0062		; P0 Switch Count
.equ P0SPH = 0x0063     ; P0 SPH
.equ P0SPL = 0x0064     ; P0 SPL

.equ P1PID = 0x0065     ; P1 PID
.equ P1PRI = 0x0066     ; P1 Priority
.equ P1SC  = 0x0067		; P1 Switch Count
.equ P1SPH = 0x0068     ; P1 SPH
.equ P1SPL = 0x0069     ; P1 SPL

.equ P2PID = 0x006A     ; P2 PID
.equ P2PRI = 0x006B     ; P2 Priority
.equ P2SC  = 0x006C		; P2 Switch Count
.equ P2SPH = 0x006D     ; P2 SPH
.equ P2SPL = 0x006E     ; P2 SPL

.equ P3PID = 0x006F     ; P3 PID
.equ P3PRI = 0x0070     ; P3 Priority
.equ P3SC  = 0x0071		; P3 Switch Count
.equ P3SPH = 0x0072     ; P3 SPH
.equ P3SPL = 0x0073     ; P3 SPL

.equ pcounter = 0x0074  ; process counter
.equ tempreg  = 0x0075  ; temporary location for holding a register when pushing/popping

.equ totalscountl=0x0076 ;total switch count low byte
.equ totalscounth=0x0077 ;total switch count high byte

.dseg         ; data segment: user RAM
.org 0x0060   ; start of data memory
	.byte 22  ; kernel variables reserved here

.cseg         ; program segment: flash ROM
.org 0x0000   ;vector table address (ref guide page 4)
	rjmp INIT ;reset vector

.org 0x000E      ;vector table address (ref guide page 4)
	rjmp OC1AISR ;OCR1A ISR vector jump

.org 0x002A   ;first address in program memory

INIT:  ; initialize the system upon reset

;set up the process control block for 
;each process. This consits of storing the high and low 
;half of the stack pointer for each process

	ldi r16,0x00 ;register to compare watchdog reset to
	in r17,MCUCSR 
	andi r17,(1<<WDRF);strip away all bits except watchdog reset
	cpse r16,r17;if r16==r17,then there has been no reset
	rjmp wdogReset;otherwise the watchdog has been reset

	ldi r16,((1<<ADLAR)|(1<<MUX2)|(1<<MUX0)|(1<<REFS0));enable ADC left adjust,
	out ADMUX,r16							;take samples from ADC5,AVCC is reference

	ldi r16,((1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0))
	out ADCSRA,r16 ;set ADC clock to 125 kHz(128 prescalar),enable ADC

	;set up the process control blocks for each process
	ldi r16,0x00   
	sts totalscountl,r16 ;zero out total switch count
	sts totalscounth,r16
	sts P0SPH,r16  ; P0 at 0x017F
	ldi r16,0x7F
	sts P0SPL,r16
	ldi r16, 0x00  ; P0 PID = 0
	sts P0PID, r16 
	ldi r16, 0xFF  ; P0 Priority = 0xFF
	sts P0PRI, r16
	ldi r16, 0x00  ; P0 Switch Count = 0
	sts P0SC, r16  

	ldi r16,0x01   ; P1 at 0x01FF
	sts P1SPH,r16
	ldi r16,0xFF
	sts P1SPL,r16
	ldi r16, 0x01  ; P1 PID = 1
	sts P1PID, r16 
	ldi r16, 0x0F  ; P1 Priority = 0x0F
	sts P1PRI, r16
	ldi r16, 0x00  ; P1 Switch Count = 0
	sts P1SC, r16  

	ldi r16,0x02   ; P2 at 0x027F
	sts P2SPH,r16
	ldi r16,0x7F
	sts P2SPL,r16
	ldi r16, 0x02  ; P2 PID = 2
	sts P2PID, r16 
	ldi r16, 0xF0  ; P2 Priority = 0xF0
	sts P2PRI, r16
	ldi r16, 0x00  ; P2 Switch Count = 0
	sts P2SC, r16  

	ldi r16,0x02   ; P3 at 0x02FF
	sts P3SPH,r16
	ldi r16,0xFF
	sts P3SPL,r16
	ldi r16, 0x03  ; P3 PID = 3
	sts P3PID, r16 
	ldi r16, 0x00  ; P3 Priority = 0x00
	sts P3PRI, r16
	ldi r16, 0x00  ; P3 Switch Count = 0
	sts P3SC, r16  
	
	;initialize process counter to 0 (Process 0)
	ldi r16,0x00
	sts pcounter,r16

	;initialize the process stacks:
	; - push the process' address to its stack
	; - push the process' registers to its stack (initially all zero)
	; - save stack pointer

	;set stack pointer to Process 0
	lds r16,P0SPH 
	out sph,r16
	lds r16,P0SPL
	out spl,r16

	ldi r16,low(P0)  ;Push P0's address
	push r16
	ldi r16,high(P0)
	push r16
	ldi r16,0x00     
	push r16		 ;Push P0's SREG
	push r16         ;push all 32 registers (initially 0)
	push r16         
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16

	lds r16,P1SPH    ;set stack pointer to P1
	out sph,r16
	lds r16,P1SPL
	out spl,r16

	ldi r16,low(P1)  ;Push P1's address
	push r16
	ldi r16,high(P1)
	push r16
	ldi r16,0x00     
	push r16         ;Push P1's SREG
	push r16         ;push all 32 registers (initially 0)
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16

	in r16,sph     ;store P1's stack pointer
	sts P1SPH,r16
	in r16,spl
	sts P1SPL,r16

	lds r16,P2SPH   ;set stack pointer to P2
	out sph,r16
	lds r16,P2SPL
	out spl,r16

	ldi r16,low(P2)  ;Push P2's address
	push r16
	ldi r16,high(P2)
	push r16
	ldi r16,0x00
	push r16         ;Push P2's SREG
	push r16         ;push all 32 registers (initially 0)
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16

	in r16,sph     ;store P2's stack pointer
	sts P2SPH,r16
	in r16,spl
	sts P2SPL,r16

	lds r16,P3SPH  ;set stack pointer to P3
	out sph,r16
	lds r16,P3SPL
	out spl,r16

	ldi r16,low(P3)  ;Push P3's address
	push r16
	ldi r16,high(P3)
	push r16
	ldi r16,0x00
	push r16         ;Push P2's SREG
	push r16         ;push all 32 registers (initially 0)
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16
	push r16

	in r16,sph       ;store P3's stack pointer
	sts P3SPH,r16
	in r16,spl
	sts P3SPL,r16

	;initialize the Timer1 interrupt for a 20ms 
	;context switch between processes
	;16MHz clock with prescaler of 256 = 62.5KHz
	;1/62.5KHz = 16us/tick
	;20ms/16us = 1250 ticks (0x04E2) (OC1A value)
	ldi r16,0x04  
	out OCR1AH,r16       ;load 0x04E2 as compare value
	ldi r16,0xE2
	out OCR1AL,r16

	in r16,TIMSK         ;enable OCR1A compare match interrupt
	ori r16,(1<<OCIE1A)
	out TIMSK,r16

	ldi r16,0x00         ;start counting at 0
	out TCNT1H,r16
	out TCNT1L,r16

	in r16,TCCR1B        ; set timer 1 for CTC mode with prescaler of 256
	ori r16,(1<<WGM12)|(1<<CS12)
	out TCCR1B,r16

	sei ;enable interrupts

	ldi r16,0xFF
	out DDRC,r16   ;set up DDRC as all output

	lds r16,P0PID  ;output P0's PID to PORTC
	out PORTC,r16
	lds r16,P0PRI  ;output P0's Priority to PORTC
	out PORTC,r16
	lds r16,P0SC   ;output P0's Switch Count to PORTC and increment it's count
	inc r16
	sts P0SC,r16
	out PORTC,r16

	lds r16,P0SPH   ;set stack pointer to p0 and output to PORTC
	out PORTC,r16
	out sph,r16
	lds r16,P0SPL
	out PORTC,r16
	out spl,r16

	in r16,WDTCR
	ori r16,((1<<WDP2)|(1<<WDP0)|(1<<WDE)) ;set watchdog for 0.5s timeout and enable it
	out WDTCR,r16

	rjmp P0         ;begin the program with P0


;Timer1 Output Compare interrupt vector routine 
;for context switching every 20ms
OC1AISR:
	sts tempreg,r0 ;temporarily save r0 to read in the SREG
    in r0,sreg     ;get/save the SREG
	push r0
	lds r0,tempreg ;restore r0
	push r0        ;push all registers
	push r1
	push r2
	push r3
	push r4
	push r5
	push r6
	push r7
	push r8
	push r9
	push r10
	push r11
	push r12
	push r13
	push r14
	push r15
	push r16
	push r17
	push r18
	push r19
	push r20
	push r21
	push r22
	push r23
	push r24
	push r25
	push r26
	push r27
	push r28
	push r29
	push r30
	push r31

	;lds r24,totalscountl ;load total switch count
	;lds r25,totalscounth
	;adiw r25:r24,1 ;increment total switch count
	;cpi r24,0x20 ;compare low half of tscount with low half of 800 (0x20)
	;ldi r26,0x03 ;compare high half of tscount with high half of 800 (0x03)
	;cpc r25,r26
	;brcs dogReset ;if carry is set, then r25:r24<800 and watchdog should be reset
	;rjmp nReset ;otherwise don't reset watchdog

dogReset:
	
	wdr

nReset:
	
	sts totalscountl,r24 ;store total switch count
	sts totalscounth,r25
	;increment process counter to determine which process
	;to switch to
	lds r16,pcounter
	inc r16          
	sts pcounter,r16
	cpi r16,0
	breq switchToP0
	cpi r16,1
	breq switchToP1
	cpi r16,2
	breq switchToP2
	rjmp switchToP3 // if we get here, it must be process 3

	;store P3's stack pointer and load P0's stack pointer
	switchToP0:
		lds r16,P0PID  ; output new process' ID
		out PORTC,r16
		lds r16,P0PRI  ; output new process' priority
		out PORTC,r16
		lds r16,P0SC   ; increment and output new process' switch count
		inc r16
		sts P0SC,r16
		out PORTC,r16

		in r16,sph
		sts P3SPH,r16
		in r16,spl
		sts P3SPL,r16
		lds r16,P0SPH
		out sph,r16
		out PORTC,r16  ; output P0 SPH to PORTC
		lds r16,P0SPL
		out spl,r16
		out PORTC,r16  ; output P0 SPL to PORTC
		rjmp start
	;store P0's stack pointer and load P1's stack pointer
	switchToP1:
		lds r16,P1PID  ; output new process' ID
		out PORTC,r16
		lds r16,P1PRI  ; output new process' priority
		out PORTC,r16
		lds r16,P1SC   ; increment and output new process' switch count
		inc r16
		sts P1SC,r16
		out PORTC,r16

		in r16,sph
		sts P0SPH,r16
		in r16,spl
		sts P0SPL,r16
		lds r16,P1SPH
		out sph,r16
		out PORTC,r16  ; output P1 SPH to PORTC
		lds r16,P1SPL
		out spl,r16
		out PORTC,r16  ; output P1 SPL to PORTC
		rjmp start
    ;store P1's stack pointer and load P2's stack pointer
	switchToP2:
		lds r16,P2PID  ; output new process' ID
		out PORTC,r16
		lds r16,P2PRI  ; output new process' priority
		out PORTC,r16
		lds r16,P2SC   ; increment and output new process' switch count
		inc r16
		sts P2SC,r16
		out PORTC,r16

		in r16,sph
		sts P1SPH,r16
		in r16,spl
		sts P1SPL,r16
		lds r16,P2SPH
		out sph,r16
		out PORTC,r16  ; output P2 SPH to PORTC
		lds r16,P2SPL
		out spl,r16
		out PORTC,r16  ; output P2 SPL to PORTC
		rjmp start
    ;store P2's stack pointer and load P3's stack pointer
	;also set pcounter to 0xFF so on the next switch it will
	;roll over back to process 0
	switchToP3:
		lds r16,P3PID  ; output new process' ID
		out PORTC,r16
		lds r16,P3PRI  ; output new process' priority
		out PORTC,r16
		lds r16,P3SC   ; increment and output new process' switch count
		inc r16
		sts P3SC,r16
		out PORTC,r16

		lds r16,pcounter
		ldi r16,0xFF
		sts pcounter,r16
		in r16,sph
		sts P2SPH,r16
		in r16,spl
		sts P2SPL,r16
		lds r16,P3SPH
		out sph,r16
		out PORTC,r16  ; output P3 SPH to PORTC
		lds r16,P3SPL
		out spl,r16
		out PORTC,r16  ; output P3 SPL to PORTC
		rjmp start

	;restores all registers for the process by popping
	;them off the stack in the reverse order
	start:
		pop r31 
		pop r30
		pop r29
		pop r28
		pop r27
		pop r26
		pop r25
		pop r24
		pop r23
		pop r22
		pop r21
		pop r20
		pop r19
		pop r18
		pop r17
		pop r16
		pop r15
		pop r14
		pop r13
		pop r12
		pop r11
		pop r10
		pop r9
		pop r8
		pop r7
		pop r6
		pop r5
		pop r4
		pop r3
		pop r2 
		pop r1
		pop r0
		sts tempreg,r0 ;store r0 so we can also restore the SREG
		pop r0         
		out sreg,r0    ;restore the SREG
		lds r0,tempreg ;restore r0
	reti ;return from the interrupt

P0: 
	ldi r16,0xFF
	out DDRC,r16
	ldi r16,0x00 ;and then outputting the process number
	out PORTC,r16

	ldi r16,0x00;set ADC port as input
	out DDRA,r16

	cli ;atomic as ADC cannot be changed by P1
	ldi r16,((1<<ADLAR)|(1<<MUX2)|(1<<MUX0)|(1<<REFS0));enable ADC left adjust,
	out ADMUX,r16							;take samples from ADC5,AVCC is reference

	sbi ADCSRA,ADSC;start ADC conversion

	convWait:
		sbic ADCSRA,ADSC ;wait for ADC conversion to finish
		rjmp convWait
	
		in r16,ADCH ;get ADC result
		cpi r16,0x8F
		sei ;end atomic
		brlo powerLoss ;if less than 2.8V (0x8F), go to power loss routine

	rjmp P0

P1:  ;Process 1
	ldi r16,0xFF
	out DDRC,r16
	ldi r16,0x01
	out PORTC,r16  ;output process number to PORTC

	ldi r16,0x00;set ADC port as input
	out DDRA,r16

	cli ;atomic as ADC cannot be changed by P1

	ldi r16,((1<<ADLAR)|(1<<MUX2)|(1<<REFS0));enable ADC left adjust,
	out ADMUX,r16							;take samples from ADC4,AVCC is reference

	sbi ADCSRA,ADSC;start ADC conversion

	convWaitP1:
		sbic ADCSRA,ADSC ;wait for ADC conversion to finish
		rjmp convWaitP1

		in r16,ADCH ;get ADC result from ADC4
		sei ;end atomic
		cpi r16,0x8F
		brlo writeZero ;if less than 2.8V, write 0 to PB7
                   ;otherwise write a 1 to PB7
		ldi r16,0xFF ;set port B as output
		out DDRB,r16
		ldi r16,0x80
		out PORTB,r16 ;output one to PB7
	
	rjmp P1

	writeZero:
		ldi r16,0xFF ;set port B as output
		out DDRB,r16
		ldi r16,0x00 ;output zero to PB7
		out PORTB,r16

	rjmp P1

P2:  ;Process 2
	ldi r16,0xFF
	out DDRC,r16
	ldi r16,0x02
	out PORTC,r16  ;output process number to PORTC
	rjmp P2

P3:  ;Process 3
	ldi r16,0xFF
	out DDRC,r16
	ldi r16,0x03
	out PORTC,r16  ;output process number to PORTC
	rjmp P3

wdogReset:
	ldi r16,0xFF ;make port c an output port
	out DDRC,r16
	ldi r16,0xFE ;output 0xFE on PORTC
	out PORTC,r16
	in r16,MCUCSR ;clear watchdog reset flag
	andi r16,~(1<<WDRF)
	out MCUCSR,r16
	rjmp End

PowerLoss:
	in r16,TCCR1B
	andi r16,~(1<<CS12) ;stop context switching by disabling timer
	out TCCR1B,r16
	in r16,WDTCR ;disable watchdog
	ori r16,(1<<WDTOE)
	out WDTCR,r16
	andi r16,~(1<<WDE)
	out WDTCR,r16
	ldi r16,0xFF ;set port c as outptu
	out DDRC,r16
	ldi r16,0xFF
	out PORTC,r16 ;output 0xFF to PORTC
	rjmp End

End:
	rjmp End